package org.ndx.jsg.guice;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.inject.Module;

/**
 * Class borrowed from http://jfpoilpret.blogspot.com/2008/07/dynamically-loading-guice-modules-as.html
 * it loads plugins in classpath in order to simplify use of Guice by avoiding the tedious process of writing down all modules in Injector loading
 * @author ndx
 *
 */
public class ClassPathModulesDiscoveryManager implements
		ModulesDiscoveryManager {
	static final private Logger logger = Logger
			.getLogger(ClassPathModulesDiscoveryManager.class.getName());
	static private final String MANIFEST_GUICE_NAME = "Guice-Modules";

	/**
	 * Default path for manifest files
	 */
	static private final String MANIFEST_PATH = "META-INF/MANIFEST.MF";

	/**
	 * List of laoded modules
	 */
	private final List<Module> modules = new ArrayList<Module>();

	/**
	 * Default constructor, using context {@link ClassLoader} to give hand to {@link ClassPathModulesDiscoveryManager#ClassPathModulesDiscoveryManager(ClassLoader)}
	 */
	public ClassPathModulesDiscoveryManager() {
		this(Thread.currentThread().getContextClassLoader());
	}

	/**
	 * Constructor parsing resources of given class loaded.
	 * Parses all manifest files and load all modules from those manifests
	 * @param loader
	 */
	public ClassPathModulesDiscoveryManager(ClassLoader loader) {
		logger.log(Level.INFO, "Loading all dynamic Guice Module");
		try {
			// Get all MANIFEST files in the classpath
			Enumeration<URL> manifests = loader.getResources(MANIFEST_PATH);
			while (manifests.hasMoreElements()) {
				addModule(loader, manifests.nextElement());
			}
		} catch (IOException e) {
			// Should not happen
			logger.log(Level.SEVERE, "Could not add all dynamic Modules", e);
		}
	}

	/**
	 * Load all module class found in manifest files
	 * @param loader class loader
	 * @param module class name
	 */
	private void addModule(ClassLoader loader, String module) {
		try {
			Class<?> clazz = Class.forName(module, true, loader);
			modules.add((Module) clazz.newInstance());
			logger.log(Level.INFO, "Guice Module %s dynamically added.",
					module);
		} catch (Exception e) {
			logger.log(Level.SEVERE, "addModule problem with: " + module, e);
		}
	}

	/**
	 * Add all modules from the input url.
	 * File at manifestUrl is supposed to be a manifest file, in which all classes at attribute {@value #MANIFEST_GUICE_NAME} will be loaded
	 * @param loader class loader
	 * @param manifestUrl manifest url
	 */
	private void addModule(ClassLoader loader, URL manifestUrl) {
		InputStream input = null;
		try {
			input = manifestUrl.openStream();
			Manifest manifest = new Manifest(input);
			String modules = manifest.getMainAttributes().getValue(
					MANIFEST_GUICE_NAME);
			if (modules != null) {
				for (String module : modules.split("[ \t]+")) {
					addModule(loader, module);
				}
			}
		} catch (IOException e) {
			logger.log(Level.SEVERE, "addModule problem with URL: "
					+ manifestUrl, e);
		} finally {
			if (input != null) {
				close(input);
			}
		}
	}

	private void close(InputStream input) {
		try {
			input.close();
		} catch (IOException e) {
			logger.log(Level.SEVERE, "an uncloseable input stream ? What the heck ???", e);
		}
	}

	/**
	 * Get the modules list
	 */
	public Iterable<Module> getModules() {
		return modules;
	}
}